#region License, Terms and Conditions
//
// Prawks - Copyright (c) 2007 Atif Aziz. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2.1 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

namespace Prawks.CodeProvisioning
{
    using System;

    using Debug = System.Diagnostics.Debug;
    using StackTrace = System.Diagnostics.StackTrace;
    using Hashtable = System.Collections.Hashtable;
    using MethodInfo = System.Reflection.MethodInfo;
    using MethodBase = System.Reflection.MethodBase;
    using PropertyInfo = System.Reflection.PropertyInfo;
    
    /// <summary>
    /// Provides linking and binding services for methods whose implementations 
    /// are provided externally by types implementing <see cref="IMethodImpl"/>. 
    /// This class cannot neither be inherited nor instantiated.
    /// </summary>
    
    public sealed class MethodImplBinder
    {
        private const string _clsGetterPrefix = "get_";
        private const string _clsSetterPrefix = "set_";
        private static readonly Hashtable _bindings = Hashtable.Synchronized(new Hashtable());

        private MethodImplBinder()
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Gets the implementation of the calling method.
        /// </summary>
        /// <returns>
        /// Returns an object that implements <see cref="IMethodImpl"/> and
        /// that provides the implementation for the calling method.
        /// </returns>

        public static IMethodImpl GetCallerImplementation()
        {
            //
            // Get the caller by walking up one frame in the stack.
            //

            StackTrace stackTrace = new StackTrace(1);
            MethodBase caller = stackTrace.GetFrame(0).GetMethod();

            //
            // If the method could not be cast into MethodInfo then
            // it is most probably a constructor.
            //
        
            MethodInfo callingMethod = caller as MethodInfo;
            
            if (callingMethod == null)
            {
                throw new BindingException(
                    string.Format("Caller is not valid for binding. {0} in {1} is most probably constructor. Use standard methods only.",
                    caller.Name, caller.DeclaringType.FullName));
            }

            return GetImplementation(callingMethod);
        }

        /// <summary>
        /// Gets the implementation of a given method.
        /// </summary>
        /// <param name="method">
        /// The method whose implementation provider is to be returned.
        /// </param>
        /// <returns>
        /// Returns an object that implements <see cref="IMethodImpl"/> and
        /// that provides the implementation for the specified method.
        /// </returns>

        public static IMethodImpl GetImplementation(MethodInfo method)
        {
            return GetImplementation(method, true);
        }
        
        /// <summary>
        /// Gets the implementation of a given method and conditionally remembers
        /// the binding.
        /// </summary>
        /// <param name="method">
        /// The method whose implementation provider is to be returned.
        /// </param>
        /// <param name="rememberBinding">
        /// Indicates whether the binding should be remembered for future
        /// recalls.
        /// </param>
        /// <returns>
        /// Returns an object that implements <see cref="IMethodImpl"/> and
        /// that provides the implementation for the specified method.
        /// </returns>

        public static IMethodImpl GetImplementation(MethodInfo method, bool rememberBinding)
        {
            //
            // Validate parameters
            //

            if (method == null)
                throw new ArgumentNullException("method");

            //
            // Check if the method has been bound before. If yes, then
            // simply return it. 

            IMethodImpl impl = (IMethodImpl) _bindings[method];
            if (impl != null)
                return impl;

            //
            // Otherwise proceed with creating the implementation and 
            // remembering the binding for future reference.
            //

            if (IsPropertyAccessor(method))
                impl = CreatePropertyAccessorImplementation(method);
            else
                impl = CreateMethodImplementation(method);

            if (rememberBinding)
                _bindings[method] = impl;

            return impl;
        }

        /// <summary>
        /// Discards the binding to a method's implementation.
        /// </summary>
        /// <param name="method">
        /// Method whose implementation to unbound.
        /// </param>

        public static void Unbind(MethodInfo method)
        {
            //
            // Validate parameters
            //

            if (method == null)
                throw new ArgumentNullException("method");

            //
            // Simply remove the method from the internal bindings table.
            //

            _bindings.Remove(method);
        }

        private static IMethodImpl CreateMethodImplementation(MethodInfo method)
        {
            Debug.Assert(method != null);

            //
            // Get the attribute MethodImplAttribute on the method whose
            // ImplType property specifies which class is the 
            // implementation provider.
            //

            MethodImplAttribute attribute = (MethodImplAttribute) Attribute.GetCustomAttribute(method, typeof(MethodImplAttribute));

            if (attribute == null)
            {
                throw new BindingException(
                    string.Format("Method {0} in {1} has no implementation specified.",
                    method.Name, method.DeclaringType.FullName));
            }

            return attribute.CreateImpl(method);
        }

        private static IMethodImpl CreatePropertyAccessorImplementation(MethodInfo accessor)
        {
            Debug.Assert(accessor != null);
            Debug.Assert(IsPropertyAccessor(accessor));

            bool isGetter = accessor.Name.StartsWith(_clsGetterPrefix);

            //
            // Get the property belonging to the accessor.
            //

            Debug.Assert(_clsGetterPrefix.Length == _clsSetterPrefix.Length);
            PropertyInfo property = accessor.ReflectedType.GetProperty(accessor.Name.Substring(_clsGetterPrefix.Length));

            //
            // Sanity checks to see if we did end up with a property whose
            // getter or setter is indeed the accessor passed to us. This
            // could happen with a method that looks like a accessor but is
            // in fact non-CLS compliant.
            //

            if (property == null)
            {
                throw new BindingException(
                    string.Format("No property found for {0} in {1}. Property binding is supported for CLS compliant types only.",
                    accessor.Name, accessor.DeclaringType.FullName));
            }

            if (isGetter)
            {
                if (property.GetGetMethod() != accessor)
                    throw new SanityException();
            }
            else 
            {
                if (property.GetSetMethod() != accessor)
                    throw new SanityException();
            }

            PropertyImplAttribute attribute = (PropertyImplAttribute) Attribute.GetCustomAttribute(property, typeof(PropertyImplAttribute));

            if (attribute == null)
            {
                throw new BindingException(
                    string.Format("Property {0} in {1} has no implementation specified.",
                    property.Name, property.DeclaringType.FullName));
            }

            return isGetter ? 
                attribute.CreateGetImpl(property) :
                attribute.CreateSetImpl(property);
        }

        private static bool IsPropertyAccessor(MethodInfo method)
        {
            Debug.Assert(method != null);

            return method.IsSpecialName && 
                (method.Name.StartsWith("get_") || method.Name.StartsWith("set_"));
        }
   }
}
